/*
 *   Copyright 2012-present OSBI Ltd
 *
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *   Unless required by applicable law or agreed to in writing, software
 *   distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *   limitations under the License.
 */

// Packages
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import uuid from 'uuid/v4';
import $ from 'jquery';
import { Grid, Row, Col } from 'react-flexbox-grid';
import {
  Button,
  Card,
  Classes,
  Dialog,
  InputGroup,
  Intent,
  Tree
} from '@blueprintjs/core';
import TruncateString from 'react-truncate-string';
import { Debounce } from 'react-throttle';

// Services
import { RepositoryService } from '../../../services';

// Actions
import { actionCreators } from '../../../actions';

// UI
import { ErrorMessage, Loading } from '../../UI';

// Utils
import { Settings } from '../../../utils';

// Constants
const REPOSITORY_OBJECTS_HEIGHT = 350;
const ERROR_MSG = 'Error fetching data from repository';

class OpenQueryDialog extends Component {
  _isMounted = false;

  state = {
    treeNodesData: [],
    treeNodesStaticData: [],
    selectedQueryNamePath: 'Please select a file...',
    repositoryObjectsHeight: REPOSITORY_OBJECTS_HEIGHT,
    isDisabledActionBtn: true,
    loading: true,
    error: false,
    errorMsg: ERROR_MSG
  };

  componentWillMount() {
    const height = $('body').height() / 2 + $('body').height() / 6;

    if (height > REPOSITORY_OBJECTS_HEIGHT) {
      this.setState({ repositoryObjectsHeight: REPOSITORY_OBJECTS_HEIGHT });
    } else {
      this.setState({ repositoryObjectsHeight: height });
    }
  }

  componentDidMount() {
    this._isMounted = true;
    this.callApiGetRepositories();
  }

  componentWillUnmount() {
    this._isMounted = false;
    RepositoryService.cancelRequest();
  }

  callApiGetRepositories = () => {
    this.setState({
      loading: true,
      error: false
    });

    RepositoryService.getRepositories()
      .then(res => {
        if (this._isMounted && res.status === 200) {
          const { data } = res;

          this.setState(
            {
              treeNodesData: this.deepCloneReplace(data, {
                repoObjects: 'childNodes',
                name: 'label'
              }),
              loading: false
            },
            () => {
              this.setState({ treeNodesStaticData: this.state.treeNodesData });
            }
          );
        } else {
          if (this._isMounted) {
            this.setState({
              loading: false,
              error: true
            });
          }
        }
      })
      .catch(error => {
        if (this._isMounted) {
          this.setState({
            loading: false,
            error: true
          });
        }
      });
  };

  deepCloneReplace(obj, replaceMap) {
    const clone = {};

    Object.keys(obj).forEach(key => {
      let newKey = key;

      if (key in replaceMap) {
        if (replaceMap.hasOwnProperty(key)) {
          newKey = replaceMap[key];
        }
      }

      clone[newKey] =
        typeof obj[key] === 'object'
          ? this.deepCloneReplace(obj[key], replaceMap)
          : obj[key];

      if (clone[key] === 'FOLDER') {
        clone['hasCaret'] = true;
        clone['icon'] = 'folder-close';
      } else if (clone[key] === 'FILE') {
        clone['icon'] = 'document';
      }
    });

    return Array.isArray(obj) && obj.length
      ? (clone.length = obj.length) && Array.from(clone)
      : Array.isArray(obj)
      ? Array.from(obj)
      : clone;
  }

  searchTreeNode = (nodeData, matchingLabel) => {
    if (
      nodeData.label.toLowerCase().indexOf(matchingLabel.toLowerCase()) !== -1
    ) {
      return nodeData;
    } else if (nodeData.childNodes !== null && nodeData.type === 'FOLDER') {
      let result = null;

      for (let i = 0; result === null && i < nodeData.childNodes.length; i++) {
        result = this.searchTreeNode(nodeData.childNodes[i], matchingLabel);
      }

      return result;
    }

    return null;
  };

  handleChangeSearch = event => {
    const { value } = event.target;
    const newTreeNodesData = this.state.treeNodesStaticData
      .map(nodeData => this.searchTreeNode(nodeData, value))
      .filter((nodeData, i, self) => nodeData && self.indexOf(nodeData) === i);

    this.setState({ treeNodesData: newTreeNodesData });
  };

  forEachNode(nodes, callback) {
    if (nodes == null) {
      return;
    }

    for (const node of nodes) {
      callback(node);
      this.forEachNode(node.childNodes, callback);
    }
  }

  handleFolderState(nodeData, isExpanded = false, iconName = 'folder-close') {
    nodeData.isExpanded = isExpanded;
    nodeData.icon = iconName;
    this.setState(this.state);
  }

  handleNodeClick = (nodeData, nodePath, event) => {
    const originallySelected = nodeData.isSelected;
    const { icon, path } = nodeData || null;

    if (!event.shiftKey) {
      this.forEachNode(this.state.treeNodesData, n => (n.isSelected = false));
    }

    nodeData.isSelected =
      originallySelected === null ? true : !originallySelected;
    this.setState(this.state);

    if (path && icon === 'document') {
      this.setState({
        selectedQueryNamePath: path,
        isDisabledActionBtn: false
      });
    } else {
      if (!nodeData.isExpanded) {
        this.handleFolderState(nodeData, true, 'folder-open');
      } else {
        this.handleFolderState(nodeData);
      }
    }
  };

  handleNodeDoubleClick = (nodeData, nodePath, event) => {
    const { type } = nodeData || null;

    if (event.detail === 2 && type === 'FILE') {
      this.handleOpenQuery();
    }
  };

  handleNodeCollapse = nodeData => {
    this.handleFolderState(nodeData);
  };

  handleNodeExpand = nodeData => {
    this.handleFolderState(nodeData, true, 'folder-open');
  };

  handleOpenQuery = () => {
    const { openQuery, onClose } = this.props;
    const { selectedQueryNamePath } = this.state;
    const file = selectedQueryNamePath;
    const params = Object.assign(
      {
        uuid: uuid().toUpperCase(),
        file,
        formatter: Settings.CELLSET_FORMATTER
      },
      Settings.PARAMS
    );

    openQuery(params);
    onClose();
  };

  renderTree() {
    const {
      treeNodesData,
      selectedQueryNamePath,
      repositoryObjectsHeight
    } = this.state;

    return (
      <Grid fluid>
        <Row>
          <Col xs>
            <Debounce time="100" handler="onChange">
              <InputGroup
                leftIcon="search"
                placeholder="Search..."
                onChange={this.handleChangeSearch}
              />
            </Debounce>
          </Col>
        </Row>
        <Row className="m-t-10">
          <Col xs>
            <Card
              style={{ height: repositoryObjectsHeight, overflowY: 'auto' }}
            >
              <Tree
                contents={treeNodesData}
                onNodeClick={this.handleNodeClick}
                onNodeDoubleClick={this.handleNodeDoubleClick}
                onNodeCollapse={this.handleNodeCollapse}
                onNodeExpand={this.handleNodeExpand}
              />
            </Card>
          </Col>
        </Row>
        <Row className="m-t-10">
          <Col xs>
            <b>
              <TruncateString text={selectedQueryNamePath} />
            </b>
          </Col>
        </Row>
      </Grid>
    );
  }

  render() {
    const { onClose } = this.props;
    const { isDisabledActionBtn, loading, error, errorMsg } = this.state;

    return (
      <Dialog
        title="Open Query"
        icon="folder-open"
        canOutsideClickClose={false}
        isOpen={true}
        onClose={onClose}
      >
        <div className={Classes.DIALOG_BODY}>
          {loading ? (
            <Loading className="m-t-20" size={30} center />
          ) : error ? (
            <ErrorMessage
              text={errorMsg}
              callApi={this.callApiGetRepositories}
            />
          ) : (
            this.renderTree()
          )}
        </div>

        {!loading && !error && (
          <div className={Classes.DIALOG_FOOTER}>
            <div className={Classes.DIALOG_FOOTER_ACTIONS}>
              <Button
                text="Open"
                intent={Intent.DANGER}
                disabled={isDisabledActionBtn}
                onClick={this.handleOpenQuery}
              />
              <Button text="Close" onClick={onClose} />
            </div>
          </div>
        )}
      </Dialog>
    );
  }
}

OpenQueryDialog.propTypes = {
  openQuery: PropTypes.func.isRequired,
  onClose: PropTypes.func.isRequired
};

const mapDispatchToProps = dispatch => ({
  openQuery: params => dispatch(actionCreators.openQuery(params))
});

export default connect(
  null,
  mapDispatchToProps
)(OpenQueryDialog);
